/*
 * MapView.java
 * Created on 12-03-2011, 02:29:55
 */
package dk.itu.KrakMap;

import java.util.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;

/**
 * A View to represent a map, that can draw Roads and routes, navigate, zoom
 * and resize. The MapView keeps track of the necesary metrics, to convert values
 * into pixel and draw them correct.
 * @author ITU-KF04-2011, Group 11
 */
public class MapView extends javax.swing.JPanel {

    private MapController controller;
    //Route planning
    private RoutePlanRequest rpr = new RoutePlanRequest(); //RoutePlanRequest for keeping track of ongoing request for routeplan
    private boolean routePlanActive = false;
    private boolean autoRoutePlan = true;
    //Things to be painted..
    private Set<? extends Road> roads; //Roads in current view to be painted
    private RoutePlan routePlan; //The current routeplan to be painted / outputted..
    private Image cachedImage; //Cache of the last set of roads drawn nicely..
    //Measurements and settings for painting the map..
    private double scaleFactor;
    private int lastHeight;
    private boolean performance = true;
    private boolean init = false;
    private boolean dragged = false;
    private double maxPixelsPerMeter = 1.75;
    private double maxMetersPerPixel;
    private double maxZoomLevel = 0.0;
    private KrakNode lastNode;
    private Viewport viewport;
    //For test:
    int repaints = 0;
    private long startTime;
    private long testTime = System.currentTimeMillis();
    //For handling keyboard gestures:
    private final static int KeyTimeLimit = 200; //Limit in ms for multi-key gestures
    private ArrayList keyList = new ArrayList(); //Mapping of KeyboardKets
    //Various lists of listeners
    private ArrayList<ViewportChangedListener> viewportChangedListeners = new ArrayList<ViewportChangedListener>();
    private ArrayList<RoutePlanRequestListener> routePlanRequestListeners = new ArrayList<RoutePlanRequestListener>();
    private ArrayList<MapMouseEventListener> mapMouseEventListeners = new ArrayList<MapMouseEventListener>();
    //Mouse listener variables.. Refactor?
    private PixelMapPoint lastClick;
    private PixelMapPoint drag;
    private Rectangle2D.Double selectionRect = new Rectangle2D.Double(0, 0, 0, 0);

    /**
     * Create a new MapView
     */
    public MapView() {
        maxMetersPerPixel = Math.pow(maxPixelsPerMeter, -1);
        initComponents();
        showRouteBtns(false);
        findRouteBtn.setVisible(false);
        checkCar.setSelected(true);
        rpr.addTravelType(TravelType.CAR);
        init = true;
        //Add various listeners..
        this.addMouseListener(new MouseListener() {

            public void mouseClicked(MouseEvent e) {
                if (routePlanActive) {
                    MapPoint clicked = pixelToUTM32(new PixelMapPoint(e.getX(), e.getY()));
                    if (e.getButton() == MouseEvent.BUTTON1) {
                        rpr.addPlace(controller.getNode(clicked));
                        autoRoutePlan();
                        if (rpr.size() > 0 && (!autoRoutePlan || !rpr.isReady())) {
                            controller.setStatusText("Vælg nu et via-punkt eller slutpunkt eller ændr evt. befordringsmiddel");
                        }
                        if (rpr.isReady()) {
                            if (!autoRoutePlan) {
                                controller.setStatusText("Vælg enten flere punkter, eller \"Find valgte rute\"");
                            }
                        }
                    } else if (e.getButton() == MouseEvent.BUTTON3 && rpr.size() > 0) {
                        KrakNode remove = getRprNode(clicked);
                        if (remove != null) {
                            rpr.removePlace(remove);
                            autoRoutePlan();
                            if (!autoRoutePlan) {
                                removeRoutePlan();
                            }
                            if (rpr.size() == 1) {
                                controller.setStatusText("vælg nu et viapunkt eller et slutpunkt.");
                            } else if (rpr.size() == 0) {
                                controller.setStatusText("vælg et startpunkt");
                            }
                        }
                    }
                    updateRouteButtons();
                    repaint();
                }
            }

            /**
             * Defines lastClick as a PixelMapPoint of e;
             * this is to keep track of where the user began pressing a mouxebutton.
             * @param e the coordinates where the mousebutton pressed.
             */
            public void mousePressed(MouseEvent e) {
                lastClick = new PixelMapPoint(e.getX(), e.getY());
                MapPoint lastClickMapPoint = pixelToUTM32(lastClick);
                lastNode = routePlanActive ? getRprNode(lastClickMapPoint) : null;
                //If a node wasn't clicked, check if the click is on a route.
                //If it is, we should make a add this node to the RoutePlanRequest.
                if (lastNode == null && routePlan != null && routePlanActive) {
                    KrakNode clickNode = controller.getNode(lastClickMapPoint);
                    Route tempRoute = routePlan.getNearRoute(clickNode, 20 / getPixelPerMeter());
                    if (tempRoute != null) {
                        rpr.insertBefore(tempRoute.getEndNode(), clickNode);
                        lastNode = clickNode;
                    }
                }
            }

            /**
             * Does zoomPixel (selectionZoom) from lastClick to e.
             * @param e the coordinates where the mousebutton is released.
             */
            public void mouseReleased(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    zoomPixel(lastClick, new PixelMapPoint(e.getX(), e.getY()));
                    //also resets the selection-rectangle from mouseDragged.
                    selectionRect = new Rectangle2D.Double(0, 0, 0, 0);
                } else if (e.getButton() == MouseEvent.BUTTON1 && dragged) {
                    dragged = false;
                }
                lastNode = null;
            }

            /**
             * This method is currently not being used in the program.
             */
            public void mouseEntered(MouseEvent e) {
            }

            /**
             * This method is currently not being used in the program.
             */
            public void mouseExited(MouseEvent e) {
            }
        });
        this.addMouseMotionListener(new MouseMotionListener() {

            /**
             * Follows the mouse as it is being moved while having a button held down;
             * furthermore depending on whether it is the left-mouseButton or the
             * right-mouseButton it will either call pan which moves the viewport,
             * or it draws a rectangle to indicate the area of the selectionZoom.
             * @param e the coordinates where the mouse have currently been moved
             * while having a mouseButton held down.
             */
            public void mouseDragged(MouseEvent e) {
                if (e.getModifiers() == MouseEvent.BUTTON1_MASK) {
                    if (lastNode != null) {
                        KrakNode newNode = controller.getNode(pixelToUTM32(new PixelMapPoint(e.getX(), e.getY())));
                        if (newNode != lastNode) {
                            rpr.replace(lastNode, newNode);
                            lastNode = newNode;
                            repaint();
                            autoRoutePlan();
                            if (!autoRoutePlan) {
                                removeRoutePlan();
                            }
                        }
                    } else {
                        pan(lastClick, new PixelMapPoint(e.getX(), e.getY()));
                        lastClick = new PixelMapPoint(e.getX(), e.getY());
                    }
                } else if (e.getModifiers() == MouseEvent.BUTTON3_MASK) {
                    drag = new PixelMapPoint(e.getX(), e.getY());
                    int x1 = lastClick.getX();
                    int y1 = lastClick.getY();
                    int x2 = drag.getX();
                    int y2 = drag.getY();
                    double l = Math.min(x1, x2);
                    double t = Math.min(y1, y2);
                    double w = Math.abs(x1 - x2);
                    double h = Math.abs(y1 - y2);
                    selectionRect = new Rectangle2D.Double(l, t, w, h);
                    // APPLY MAGIC SOLUTION ON FOLLOWING LINE
                    repaint();
                } else if (e.getModifiers() == MouseEvent.BUTTON3_MASK) {
                    drag = new PixelMapPoint(e.getX(), e.getY());
                    int x1 = lastClick.getX();
                    int y1 = lastClick.getY();
                    int x2 = drag.getX();
                    int y2 = drag.getY();
                    double l = Math.min(x1, x2);
                    double t = Math.min(y1, y2);
                    double w = Math.abs(x1 - x2);
                    double h = Math.abs(y1 - y2);
                    selectionRect = new Rectangle2D.Double(l, t, w, h);
                    repaint();
                }
            }

            /**
             * Fire a MapMouseMovedEvent when the mouse is moved on the map..
             */
            public void mouseMoved(MouseEvent e) {
                fireMapMouseMovedEvent(pixelToUTM32(new PixelMapPoint(e.getX(), e.getY())));
            }
        });
        this.addMouseWheelListener(new MouseWheelListener() {

            /**
             * Method for zooming with the mouseWheel.
             */
            public void mouseWheelMoved(MouseWheelEvent e) {
                int notches = e.getWheelRotation();
                zoom(-notches * 4, new PixelMapPoint(e.getX(), e.getY()));
            }
        });
        this.addKeyListener(new KeyListener() {

            /**
             * This method is currently not being used in the program.
             */
            public void keyTyped(KeyEvent e) {
            }

            /**
             * Handles the keyOperations. for all keys pressed, both multiples and otherwise.
             */
            public void keyPressed(KeyEvent e) {
                setFocusTraversalKeysEnabled(true);
                int keyCode = e.getKeyCode();
                switch (keyCode) {
                    case KeyEvent.VK_SHIFT:
                        keyList.add(KeyEvent.VK_SHIFT);
                        break;
                    case KeyEvent.VK_UP:
                        if (keyList.contains(KeyEvent.VK_SHIFT)) {
                            panN(50);
                        } else {
                            panN(10);
                        }
                        break;
                    case KeyEvent.VK_DOWN:
                        if (keyList.contains(KeyEvent.VK_SHIFT)) {
                            panS(50);
                        } else {
                            panS(10);
                        }
                        break;
                    case KeyEvent.VK_LEFT:
                        if (keyList.contains(KeyEvent.VK_SHIFT)) {
                            panW(50);
                        } else {
                            panW(10);
                        }
                        break;
                    case KeyEvent.VK_RIGHT:
                        if (keyList.contains(KeyEvent.VK_SHIFT)) {
                            panE(50);
                        } else {
                            panE(10);
                        }
                        break;
                    case KeyEvent.VK_O:
                        if (keyList.contains(KeyEvent.VK_SHIFT)) {
                            zoom(-20);
                        } else {
                            zoom(-5);
                        }
                        break;
                    case KeyEvent.VK_I:
                        if (keyList.contains(KeyEvent.VK_SHIFT)) {
                            zoom(20);
                        } else {
                            zoom(5);
                        }
                        break;
                }
            }

            /*
             * For the double keyoperations you need to press several keys before releasing any of them.
             * If a key needed for a multiple-key-operation is released prematurely this method makes sure
             * that the operation won't be executed as a multiple-key-operation
             */
            public void keyReleased(KeyEvent e) {
                int keyCode = e.getKeyCode();
                switch (keyCode) {
                    case KeyEvent.VK_SHIFT:
                        keyList.clear();
                        break;
                }
            }
        });
        this.addComponentListener(new ComponentAdapter() {

            public void componentResized(ComponentEvent e) {
                if (lastHeight == 0) {
                    lastHeight = getHeight();
                }
                setViewport(
                        pixelToUTM32(new PixelMapPoint(0, getHeight() * 2 - lastHeight)),
                        pixelToUTM32(new PixelMapPoint(getWidth(), getHeight() - lastHeight)));
                lastHeight = getHeight();
            }
        });
        //Set focusable, for keys to work...
        this.setFocusable(true);
        this.setFocusTraversalKeysEnabled(true);
    }

    public void setController(MapController c) {
        controller = c;
    }

    /**
     * Set the Viewport for the MapView
     * @param viewport The new viewport
     */
    public void setViewport(Viewport v) {
        //Check if pixelPerMeter gets too high. If it does, change viewport to
        //match maxPixelPerMeter.
        if (getPixelPerMeter(v) > maxPixelsPerMeter) {
            double diff = (maxMetersPerPixel + 0.005) * getWidth() - (v.getUpRight().getX() - v.getDownLeft().getX());
            v.getDownLeft().setX(v.getDownLeft().getX() - diff / 2);
            v.getUpRight().setX(v.getUpRight().getX() + diff / 2);
            if (!viewport.equals(v)) {
                zoomUTM32(v.getDownLeft(), v.getUpRight());
            }
            return;
        }
        //Check if pixelPerMeter gets too low..
        if (maxZoomLevel > getPixelPerMeter(v)) {
            return;
        }
        this.viewport = v;
        calcScaleFactor();
        if (maxZoomLevel < getPixelPerMeter() && maxZoomLevel == 0.0) {
            maxZoomLevel = getPixelPerMeter();
        }
        if (this.getPixelPerMeter() < 0.3) {
            performance = true;
        }
        fireViewportChangedEvent();
    }

    /**
     * Get the node in route plan request from a MapPoint
     * @param point The MapPoint to get the node from
     * @return The found KrakNode, or null if none is found within 20 pixel.
     */
    private KrakNode getRprNode(MapPoint point) {
        return getRprNode(point, 20);
    }

    /**
     * Get the node in route plan request from a MapPoint
     * @param point The MapPoint to get the node from
     * @param radius How big a radius from the original point is allowed?
     * @return The found KrakNode, or null if none is found within the radius.
     */
    private KrakNode getRprNode(MapPoint point, int radius) {
        KrakNode res = rpr.getNearestNode(point);
        //Remove node only if it is within 20 pixel..
        if (res != null && res.getMapPoint().dist(point) * getPixelPerMeter() <= radius) {
            return res;
        } else {
            return null;
        }
    }

    /**
     * Get a copy of the current Viewport.
     * @return
     */
    public Viewport getViewport() {
        return viewport.clone();
    }

    /**
     * Set a new Viewport from MapPoints
     * @param downLeft The downLeft UTM32 MapPoint
     * @param upRight The upRight UTM32 MapPoint
     */
    public void setViewport(MapPoint downLeft, MapPoint upRight) {
        setViewport(new Viewport(downLeft, upRight));
    }

    /**
     * Set a new Viewport from PixelMapPoints
     * @param downLeft The downLeft PixelMapPoint
     * @param upRight The upRight PixelMapPoint
     */
    public void setViewport(PixelMapPoint downLeft, PixelMapPoint upRight) {
        setViewport(pixelToUTM32(downLeft), pixelToUTM32(upRight));
    }

    /**
     * Add new list of roads and paint them
     * @param roads The list of roads to paint
     */
    public void setRoads(Set<? extends Road> roads) {
        this.roads = roads;
        repaint();
    }

    /**
     * Set and paint a new RoutePlan on the map
     * @param route The RoutePlan to be set
     */
    public void setRoutePlan(RoutePlan route) {
        routePlan = route;
        repaint();
    }

    /**
     * Clear the current RoutePlan from the map
     */
    public void clearRoutePlan() {
        routePlan = null;
        repaint();
    }

    /**
     * Convert a UTM32 MapPoint to a PixelMapPoint
     * @param point The MapPoint to convert
     * @return The converted point as PixelMapPoint
     */
    private PixelMapPoint UTM32ToPixel(MapPoint point) {
        return new PixelMapPoint(
                (int) Math.round((point.getX() - viewport.getDownLeft().getX()) * scaleFactor),
                getHeight() - (int) Math.round((point.getY() - viewport.getDownLeft().getY()) * scaleFactor));
    }

    /**
     * Convert PixelMapPoint to a UTM32 MapPoint
     * @param point The PixelMapPoint to convert
     * @return The converted point as UTM32 MapPoint
     */
    private MapPoint pixelToUTM32(PixelMapPoint point) {
        return new MapPoint(
                point.getX() / scaleFactor + viewport.getDownLeft().getX(),
                (getHeight() - point.getY()) / scaleFactor + viewport.getDownLeft().getY());
    }

    /**
     * This method is used to draw a road.
     * @param road the road to be drawn.
     * @param g the Graphics2D object to be used for the drawing
     * @param c the road's color
     * @param stroke A basicStroke object used to set the Stroke of the graphics2D object.
     */
    private void drawRoad(Road road, Graphics2D g, Color c, BasicStroke stroke) {
        PixelMapPoint start = UTM32ToPixel(road.getStart());
        PixelMapPoint end = UTM32ToPixel(road.getEnd());
        g.setStroke(stroke);
        g.setColor(c);
        g.drawLine(start.getX(), start.getY(), end.getX(), end.getY());
    }

    /**
     * A method that is used for drawing all the roads in a given queue.
     * @param roads a MaxPriorityQueue that holds the roads to be drawn
     * @param g the graphics object being used for the drawing itself.
     */
    private void drawRoads(MaxPQ<Road> roads, Graphics2D g) {
        RoadType rt = null;
        float width = 0;
        float minWidth = 0;
        Color c = null;
        if (performance) {
            //Paint fill only
            for (Road r : roads) {
                if (rt == null || !rt.equals(r.getType())) {
                    rt = r.getType();
                    minWidth = rt.getMinSize();
                    width = rt.getSize() * (float) scaleFactor;
                    if (minWidth > width) {
                        width = minWidth;
                    }
                    c = r.getColor() == null ? rt.getColor() : r.getColor();
                }
                drawRoad(r, g, c, new BasicStroke(width));
            }
        } else {
            //Paint stroke and fill
            for (Road r : roads) {
                //Paint all strokes.. Change style when we hit new RoadType
                if (rt == null || !rt.equals(r.getType())) {
                    rt = r.getType();
                    minWidth = rt.getMinSize();
                    width = (float) (rt.getSize() * scaleFactor);
                    if (minWidth > width) {
                        width = minWidth;
                    }
                    if (rt.isStroke()) {
                        c = r.getStrokeColor() == null ? rt.getStrokeColor() : r.getStrokeColor();
                    } else {
                        c = r.getColor() == null ? rt.getColor() : r.getColor();
                    }
                }
                if (rt.isStroke()) {
                    drawRoad(r, g, c, new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
                }
            }
            rt = null;
            for (Road r : roads) {
                //Paint all fills.. Change style when we hit new RoadType
                if (rt == null || !rt.equals(r.getType())) {
                    rt = r.getType();
                    if (rt.isStroke()) {
                        width = (float) (rt.getSize() * scaleFactor * rt.getStrokeFactor());
                        minWidth = (float) (rt.getMinSize() * rt.getStrokeFactor());
                    } else {
                        width = (float) (rt.getSize() * scaleFactor);
                        minWidth = rt.getMinSize();
                    }
                    if (minWidth > width) {
                        width = minWidth;
                    }
                    c = r.getColor() == null ? r.getType().getColor() : r.getColor();
                }
                drawRoad(r, g, c, new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            }
        }
    }

    /*
     * draw the user-requested route including viapoints. Puts all the routes in a path
     * to enable a nice and smooth drawing of the entire routePlan.
     */
    private void drawRoutePlan(Graphics2D g) {
        MapPoint lastVisited = rpr.getPlaces().get(0).getMapPoint();
        PixelMapPoint pmp = UTM32ToPixel(rpr.getPlaces().get(0).getMapPoint());
        GeneralPath gp = new GeneralPath();
        gp.moveTo(pmp.getX(), pmp.getY());
        if (routePlan.getRoads() != null) {
            for (Road road : routePlan.getRoads()) {
                PixelMapPoint start = UTM32ToPixel(road.getStart());
                PixelMapPoint end = UTM32ToPixel(road.getEnd());
                //for each road a line is being added to the path, but it is necessary to check whether the next line is to the next roads start- or endpoint as it is not always the endpoint.
                if (lastVisited.equals(road.getEnd())) {
                    gp.lineTo(start.getX(), start.getY());
                    lastVisited = road.getStart();
                } else {
                    gp.lineTo(end.getX(), end.getY());
                    lastVisited = road.getEnd();
                }
            }
        }
        //int roadSize = (int) Math.ceil((road.getType().getStrokeSize() * scaleFactor * 2.5));
        //  int strokeSize = roadSize < 2 ? 2 : roadSize;
        g.setStroke(new BasicStroke(7, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        g.setColor(new Color(0, 0, (float) 1.0, (float) 0.45));
        //g.drawLine(start.getX(), start.getY(), end.getX(), end.getY());
        g.draw(gp);
    }

    /*
     * Method to draw roads on the current viewport.
     */
    private void drawRoadMap(Graphics2D graphics) {
        Graphics2D g = (Graphics2D) graphics.create();
        if (roads != null) {
            MaxPQ<Road> roadsPq = new MaxPQ<Road>(new Comparator<Road>() {

                public int compare(Road r1, Road r2) {
                    if (r1.getType().getPriority() < r2.getType().getPriority()) {
                        return -1;
                    } else if (r1.getType().getPriority() > r2.getType().getPriority()) {
                        return 1;
                    } else {
                        return 0;
                    }
                }
            });
            for (Road r : roads) {
                roadsPq.insert(r);
            }
            drawRoads(roadsPq, g);
        }
    }

    /*
     * the method responsible for the drawing of the map. The other drawing methods
     * and visual features are controlled in this method.
     */
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        if (cachedImage == null) {
            GraphicsConfiguration gc = getGraphicsConfiguration();
            cachedImage = gc.createCompatibleImage(getWidth(), getHeight(), Transparency.TRANSLUCENT);
            Graphics2D gImg = (Graphics2D) cachedImage.getGraphics();
            if (!performance) {
                gImg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
            } else {
                gImg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_OFF);
            }
            drawRoadMap(gImg);
            gImg.dispose();
        }
        g2d.drawImage(cachedImage, 0, 0, null);
        if (performance) {
            cachedImage = null;
            performance = false;
            repaint();
        }
        if (routePlan != null && routePlanActive) {
            drawRoutePlan(g2d);
        }
        if (rpr.size() > 0 && routePlanActive) {
            int i = 1;
            for (KrakNode n : rpr.getPlaces()) {
                PixelMapPoint pmp = UTM32ToPixel(n.getMapPoint());
                g2d.setColor(Color.magenta);
                g2d.fillOval(pmp.getX() - 10, pmp.getY() - 10, 20, 20);
                g2d.setColor(Color.BLACK);
                g2d.drawString(i + "", pmp.getX() - (i > 9 ? 6 : 3), pmp.getY() + 5);
                i++;
            }
        }


        g2d.setStroke(new BasicStroke(3));
        g2d.setColor(Color.gray);
        g2d.draw(selectionRect);
        g2d.setStroke(new BasicStroke(1));
    }

    /*
     * repaint when the user interacts with the map.
     */
    @Override
    public void repaint() {
        repaints++;
        super.repaint();
        this.setFocusable(true);
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        navWestBtn = new javax.swing.JButton();
        navNorthBtn = new javax.swing.JButton();
        navEastBtn = new javax.swing.JButton();
        navZoomOutBtn = new javax.swing.JButton();
        navSouthBtn = new javax.swing.JButton();
        navZoomInBtn = new javax.swing.JButton();
        RouteToggle = new javax.swing.JToggleButton();
        routePlanPanel = new javax.swing.JPanel();
        checkCar = new javax.swing.JCheckBox();
        checkBicycle = new javax.swing.JCheckBox();
        checkWalk = new javax.swing.JCheckBox();
        checkFerry = new javax.swing.JCheckBox();
        findRouteBtn = new javax.swing.JButton();
        fjernRouteBtn = new javax.swing.JButton();
        routePlanGuide = new javax.swing.JLabel();
        autoPaintRoute = new javax.swing.JCheckBox();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        swapA = new javax.swing.JTextField();
        swapB = new javax.swing.JTextField();
        swapBtn = new javax.swing.JButton();

        setBackground(new java.awt.Color(255, 245, 201));
        setBorder(javax.swing.BorderFactory.createEtchedBorder());
        setPreferredSize(new java.awt.Dimension(400, 400));

        navWestBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dk/itu/KrakMap/left.png"))); // NOI18N
        navWestBtn.setFocusPainted(false);
        navWestBtn.setFocusable(false);
        navWestBtn.setMargin(new java.awt.Insets(2, 4, 2, 4));
        navWestBtn.setRequestFocusEnabled(false);
        navWestBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                navWestBtnActionPerformed(evt);
            }
        });

        navNorthBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dk/itu/KrakMap/up.png"))); // NOI18N
        navNorthBtn.setFocusPainted(false);
        navNorthBtn.setFocusable(false);
        navNorthBtn.setMargin(new java.awt.Insets(2, 4, 2, 4));
        navNorthBtn.setRequestFocusEnabled(false);
        navNorthBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                navNorthBtnActionPerformed(evt);
            }
        });

        navEastBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dk/itu/KrakMap/right.png"))); // NOI18N
        navEastBtn.setFocusPainted(false);
        navEastBtn.setFocusable(false);
        navEastBtn.setMargin(new java.awt.Insets(2, 4, 2, 4));
        navEastBtn.setRequestFocusEnabled(false);
        navEastBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                navEastBtnActionPerformed(evt);
            }
        });

        navZoomOutBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dk/itu/KrakMap/out.png"))); // NOI18N
        navZoomOutBtn.setFocusPainted(false);
        navZoomOutBtn.setFocusable(false);
        navZoomOutBtn.setMargin(new java.awt.Insets(2, 4, 2, 4));
        navZoomOutBtn.setRequestFocusEnabled(false);
        navZoomOutBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                navZoomOutBtnActionPerformed(evt);
            }
        });

        navSouthBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dk/itu/KrakMap/down.png"))); // NOI18N
        navSouthBtn.setFocusPainted(false);
        navSouthBtn.setFocusable(false);
        navSouthBtn.setMargin(new java.awt.Insets(2, 4, 2, 4));
        navSouthBtn.setRequestFocusEnabled(false);
        navSouthBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                navSouthBtnActionPerformed(evt);
            }
        });

        navZoomInBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/dk/itu/KrakMap/in.png"))); // NOI18N
        navZoomInBtn.setFocusPainted(false);
        navZoomInBtn.setFocusable(false);
        navZoomInBtn.setMargin(new java.awt.Insets(2, 4, 2, 4));
        navZoomInBtn.setRequestFocusEnabled(false);
        navZoomInBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                navZoomInBtnActionPerformed(evt);
            }
        });

        RouteToggle.setText("Rute Plan");
        RouteToggle.setFocusPainted(false);
        RouteToggle.setFocusable(false);
        RouteToggle.setMaximumSize(new java.awt.Dimension(35, 23));
        RouteToggle.setMinimumSize(new java.awt.Dimension(35, 23));
        RouteToggle.setRequestFocusEnabled(false);
        RouteToggle.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                RouteToggleActionPerformed(evt);
            }
        });

        routePlanPanel.setPreferredSize(new java.awt.Dimension(155, 350));

        checkCar.setText("Bil");
        checkCar.setFocusPainted(false);
        checkCar.setFocusable(false);
        checkCar.setRequestFocusEnabled(false);
        checkCar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                checkCarActionPerformed(evt);
            }
        });

        checkBicycle.setText("Cykel");
        checkBicycle.setFocusPainted(false);
        checkBicycle.setFocusable(false);
        checkBicycle.setMaximumSize(new java.awt.Dimension(35, 23));
        checkBicycle.setMinimumSize(new java.awt.Dimension(35, 23));
        checkBicycle.setRequestFocusEnabled(false);
        checkBicycle.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                checkBicycleActionPerformed(evt);
            }
        });

        checkWalk.setText("Gå");
        checkWalk.setFocusPainted(false);
        checkWalk.setFocusable(false);
        checkWalk.setMaximumSize(new java.awt.Dimension(35, 23));
        checkWalk.setMinimumSize(new java.awt.Dimension(35, 23));
        checkWalk.setRequestFocusEnabled(false);
        checkWalk.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                checkWalkActionPerformed(evt);
            }
        });

        checkFerry.setText("Færger");
        checkFerry.setFocusPainted(false);
        checkFerry.setFocusable(false);
        checkFerry.setMaximumSize(new java.awt.Dimension(35, 23));
        checkFerry.setMinimumSize(new java.awt.Dimension(35, 23));
        checkFerry.setRequestFocusEnabled(false);
        checkFerry.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                checkFerryActionPerformed(evt);
            }
        });

        findRouteBtn.setText("Find valgte rute");
        findRouteBtn.setEnabled(false);
        findRouteBtn.setFocusPainted(false);
        findRouteBtn.setFocusable(false);
        findRouteBtn.setMaximumSize(new java.awt.Dimension(35, 23));
        findRouteBtn.setMinimumSize(new java.awt.Dimension(35, 23));
        findRouteBtn.setRequestFocusEnabled(false);
        findRouteBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                findRouteBtnActionPerformed(evt);
            }
        });

        fjernRouteBtn.setText("Fjern aktuel rute");
        fjernRouteBtn.setEnabled(false);
        fjernRouteBtn.setFocusPainted(false);
        fjernRouteBtn.setRequestFocusEnabled(false);
        fjernRouteBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                fjernRouteBtnActionPerformed(evt);
            }
        });

        routePlanGuide.setFont(new java.awt.Font("Tahoma", 0, 10));
        routePlanGuide.setText("<html><b>Venstreklik:</b> Tilføj punkt<br /><b>Højreklik:</b> Fjern punkt<br /><b>Træk i punkt:</b><br /> flyt dette punkt<br /><b>Træk i rute:</b><br /> ændre denne del af ruten</html>");
        routePlanGuide.setVerticalAlignment(javax.swing.SwingConstants.TOP);
        routePlanGuide.setFocusable(false);
        routePlanGuide.setOpaque(true);
        routePlanGuide.setRequestFocusEnabled(false);

        autoPaintRoute.setFont(new java.awt.Font("Tahoma", 0, 10));
        autoPaintRoute.setSelected(true);
        autoPaintRoute.setText("Opdatér rute automatisk");
        autoPaintRoute.setFocusPainted(false);
        autoPaintRoute.setFocusable(false);
        autoPaintRoute.setRequestFocusEnabled(false);
        autoPaintRoute.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                autoPaintRouteActionPerformed(evt);
            }
        });

        jLabel1.setFont(new java.awt.Font("Tahoma", 1, 12));
        jLabel1.setText("Byt punkter:");
        jLabel1.setRequestFocusEnabled(false);

        jLabel2.setFont(new java.awt.Font("Tahoma", 0, 10));
        jLabel2.setText("<html>Indtast talkoden for to<br />punkter du ønsker at bytte");
        jLabel2.setFocusable(false);
        jLabel2.setRequestFocusEnabled(false);

        swapBtn.setText("Byt");
        swapBtn.setFocusPainted(false);
        swapBtn.setFocusable(false);
        swapBtn.setRequestFocusEnabled(false);
        swapBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                swapBtnActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout routePlanPanelLayout = new javax.swing.GroupLayout(routePlanPanel);
        routePlanPanel.setLayout(routePlanPanelLayout);
        routePlanPanelLayout.setHorizontalGroup(
            routePlanPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(routePlanPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(routePlanPanelLayout.createSequentialGroup()
                        .addComponent(checkCar, javax.swing.GroupLayout.DEFAULT_SIZE, 143, Short.MAX_VALUE)
                        .addContainerGap())
                    .addGroup(routePlanPanelLayout.createSequentialGroup()
                        .addComponent(checkFerry, javax.swing.GroupLayout.DEFAULT_SIZE, 137, Short.MAX_VALUE)
                        .addGap(12, 12, 12))))
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, routePlanPanelLayout.createSequentialGroup()
                .addGap(8, 8, 8)
                .addComponent(findRouteBtn, javax.swing.GroupLayout.DEFAULT_SIZE, 137, Short.MAX_VALUE)
                .addContainerGap())
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, routePlanPanelLayout.createSequentialGroup()
                .addGap(8, 8, 8)
                .addComponent(fjernRouteBtn, javax.swing.GroupLayout.DEFAULT_SIZE, 137, Short.MAX_VALUE)
                .addContainerGap())
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(checkBicycle, javax.swing.GroupLayout.DEFAULT_SIZE, 143, Short.MAX_VALUE)
                .addContainerGap())
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(checkWalk, javax.swing.GroupLayout.DEFAULT_SIZE, 137, Short.MAX_VALUE)
                .addGap(12, 12, 12))
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(routePlanPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(routePlanPanelLayout.createSequentialGroup()
                        .addComponent(autoPaintRoute)
                        .addContainerGap())
                    .addGroup(routePlanPanelLayout.createSequentialGroup()
                        .addComponent(routePlanGuide, javax.swing.GroupLayout.DEFAULT_SIZE, 137, Short.MAX_VALUE)
                        .addGap(8, 8, 8))))
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addContainerGap(67, Short.MAX_VALUE))
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, 135, Short.MAX_VALUE)
                .addContainerGap())
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(swapA, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(swapB, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(swapBtn)
                .addContainerGap(22, Short.MAX_VALUE))
        );
        routePlanPanelLayout.setVerticalGroup(
            routePlanPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(routePlanPanelLayout.createSequentialGroup()
                .addComponent(checkCar)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(checkBicycle, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(checkWalk, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(3, 3, 3)
                .addComponent(checkFerry, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(findRouteBtn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(fjernRouteBtn)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(routePlanGuide, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(autoPaintRoute)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(routePlanPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(swapA, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(swapB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(swapBtn))
                .addContainerGap(23, Short.MAX_VALUE))
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(navWestBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(navZoomInBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(navNorthBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(navZoomOutBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(navSouthBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(navEastBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 134, Short.MAX_VALUE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(routePlanPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(RouteToggle, javax.swing.GroupLayout.PREFERRED_SIZE, 151, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(RouteToggle, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(routePlanPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(navNorthBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(navZoomInBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(navZoomOutBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(navSouthBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(navEastBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(navWestBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents
        private void navWestBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_navWestBtnActionPerformed
            panW(20);
    }//GEN-LAST:event_navWestBtnActionPerformed
    private void navNorthBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_navNorthBtnActionPerformed
        panN(20);
    }//GEN-LAST:event_navNorthBtnActionPerformed
    private void navEastBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_navEastBtnActionPerformed
        panE(20);
    }//GEN-LAST:event_navEastBtnActionPerformed
    private void navZoomOutBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_navZoomOutBtnActionPerformed
        zoom(-20);
    }//GEN-LAST:event_navZoomOutBtnActionPerformed
    private void navSouthBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_navSouthBtnActionPerformed
        panS(20);
    }//GEN-LAST:event_navSouthBtnActionPerformed
    private void navZoomInBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_navZoomInBtnActionPerformed
        zoom(20);
    }//GEN-LAST:event_navZoomInBtnActionPerformed

    /**
     * A private method invoked when the user attempts to find a route manually.
     * @param evt an ActionEvent that carries out the users request.
     */
    private void findRouteBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_findRouteBtnActionPerformed
        findRouteBtn.setSelected(true);
        controller.setStatusText("Finder hurtigste rute...");
        fireRoutePlanRequestEvent();
    }//GEN-LAST:event_findRouteBtnActionPerformed

    /**
     * makes sure the buttons are always either enabled og disabled at the right times
     */
    private void updateRouteButtons() {
        routePlanActive = RouteToggle.isSelected();
        showRouteBtns(routePlanActive);
        findRouteBtn.setEnabled(rpr.isReady());
        findRouteBtn.setVisible(!autoRoutePlan);
        fjernRouteBtn.setEnabled(rpr.size() > 0);
        repaint();
    }

    /*
     * updates route buttons
     */
    private void RouteToggleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_RouteToggleActionPerformed
        updateRouteButtons();
    }//GEN-LAST:event_RouteToggleActionPerformed

    /**
     * deselects all transportbuttons and clears the routePlanRequest.
     * @param evt ActionEvent.
     */
    private void fjernRouteBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fjernRouteBtnActionPerformed
        rpr = new RoutePlanRequest();
        routePlan = null;
        checkCar.setSelected(false);
        checkBicycle.setSelected(false);
        checkWalk.setSelected(false);
        checkFerry.setSelected(false);
        controller.setStatusText("Rute fjernet!");
        updateRouteButtons();
    }//GEN-LAST:event_fjernRouteBtnActionPerformed

    /**
     * add the TravelType 'CAR' to the routePlanRequest, updates buttons
     * and checks if a route should be found automatically.
     * @param evt an ActionEvent that adds the TravelType 'CAR' to the routePlanRequest.
     */
	private void checkCarActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_checkCarActionPerformed
    {//GEN-HEADEREND:event_checkCarActionPerformed
        if (checkCar.isSelected()) {
            rpr.addTravelType(TravelType.CAR);
        } else {
            rpr.removeTravelType(TravelType.CAR);
        if(rpr.size()>1 && !rpr.isReady()) {controller.setStatusText("Vælg minimum ét befordringsmiddel for at finde en rute"); }
        }
        autoRoutePlan();
        updateRouteButtons();
    }//GEN-LAST:event_checkCarActionPerformed

    /**
     * add the TravelType 'BICYCLE' to the routePlanRequest, updates buttons
     * and checks if a route should be found automatically.
     * @param evt an ActionEvent that adds the TravelType 'BICYCLE' to the routePlanRequest.
     */
    private void checkBicycleActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_checkBicycleActionPerformed
    {//GEN-HEADEREND:event_checkBicycleActionPerformed
        if (checkBicycle.isSelected()) {
            rpr.addTravelType(TravelType.BICYCLE);
        } else {
            rpr.removeTravelType(TravelType.BICYCLE);
        if(rpr.size()>1 && !rpr.isReady()) {controller.setStatusText("Vælg minimum ét befordringsmiddel for at finde en rute"); }
        }
        autoRoutePlan();
        updateRouteButtons();
    }//GEN-LAST:event_checkBicycleActionPerformed
    /**
     * add the TravelType 'WALK' to the routePlanRequest, updates buttons
     * and checks if a route should be found automatically.
     * @param evt an ActionEvent that adds the TravelType 'WALK' to the routePlanRequest.
     */
    private void checkWalkActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_checkWalkActionPerformed
    {//GEN-HEADEREND:event_checkWalkActionPerformed
        if (checkWalk.isSelected()) {
            rpr.addTravelType(TravelType.WALK);
        } else {
            rpr.removeTravelType(TravelType.WALK);
        if(rpr.size()>1 && !rpr.isReady()) {controller.setStatusText("Vælg minimum ét befordringsmiddel for at finde en rute"); }
        }
        autoRoutePlan();
        updateRouteButtons();
    }//GEN-LAST:event_checkWalkActionPerformed

    /**
     * add the TravelType 'FERRY' to the routePlanRequest, updates buttons
     * and checks if a route should be found automatically.
     * @param evt an ActionEvent that adds the TravelType 'FERRY' to the routePlanRequest.
     */
    private void checkFerryActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_checkFerryActionPerformed
    {//GEN-HEADEREND:event_checkFerryActionPerformed
        if (checkFerry.isSelected()) {
            rpr.addTravelType(TravelType.FERRY);
        } else {
            rpr.removeTravelType(TravelType.FERRY);
        if(rpr.size()>1 && !rpr.isReady()) {controller.setStatusText("Vælg minimum ét befordringsmiddel for at finde en rute"); }
        }
        autoRoutePlan();
        updateRouteButtons();
    }//GEN-LAST:event_checkFerryActionPerformed

    /**
     * Determines whether the route should be drawn automatically as the user adds points
     * or if it should be done manually by the user.
     * @param evt ActionEvent
     */
    private void autoPaintRouteActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_autoPaintRouteActionPerformed
    {//GEN-HEADEREND:event_autoPaintRouteActionPerformed
        autoRoutePlan = autoPaintRoute.isSelected();
        autoRoutePlan();

        updateRouteButtons();
    }//GEN-LAST:event_autoPaintRouteActionPerformed

    /**
     * swap the two points in textboxes swapA and swapB, on the route if the parameters
     * given in the textboxes are valid.
     * @param evt ActionEvent
     */
    private void swapBtnActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_swapBtnActionPerformed
    {//GEN-HEADEREND:event_swapBtnActionPerformed
        int point1, point2;
        try {
            point1 = Integer.parseInt(swapA.getText());
            point2 = Integer.parseInt(swapB.getText());
            //check whether hte input from the user is valid or not.
            boolean suc = rpr.swapPoints(point1 - 1, point2 - 1);
            if (!suc) {
                controller.setStatusText("<html><span color=red>Kunne ikke finde de ønskede punkter.</span></html>");
            } else {
                autoRoutePlan();
                if (!autoRoutePlan) {
                    removeRoutePlan();
                    repaint();
                }
            }
            swapA.setFocusable(false);
            swapB.setFocusable(false);
            this.setFocusable(true);
            swapA.setFocusable(true);
            swapB.setFocusable(true);
        } catch (NumberFormatException ex) {
            controller.setStatusText("<html><span color=red>Kunne ikke finde de ønskede punkter.</span></html>");
            swapA.setFocusable(false);
            swapB.setFocusable(false);
            this.setFocusable(true);
            swapA.setFocusable(true);
            swapB.setFocusable(true);
        }
    }//GEN-LAST:event_swapBtnActionPerformed
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JToggleButton RouteToggle;
    private javax.swing.JCheckBox autoPaintRoute;
    private javax.swing.JCheckBox checkBicycle;
    private javax.swing.JCheckBox checkCar;
    private javax.swing.JCheckBox checkFerry;
    private javax.swing.JCheckBox checkWalk;
    private javax.swing.JButton findRouteBtn;
    private javax.swing.JButton fjernRouteBtn;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JButton navEastBtn;
    private javax.swing.JButton navNorthBtn;
    private javax.swing.JButton navSouthBtn;
    private javax.swing.JButton navWestBtn;
    private javax.swing.JButton navZoomInBtn;
    private javax.swing.JButton navZoomOutBtn;
    private javax.swing.JLabel routePlanGuide;
    private javax.swing.JPanel routePlanPanel;
    private javax.swing.JTextField swapA;
    private javax.swing.JTextField swapB;
    private javax.swing.JButton swapBtn;
    // End of variables declaration//GEN-END:variables

    /**
     * add a ViewportChangedListener to viewportChangedListeners list.
     * @param listener ViewportChangedListener
     */
    public synchronized void addViewportListener(ViewportChangedListener listener) {
        viewportChangedListeners.add(listener);
    }

    /**
     * remove a ViewportChangedListener from viewportChangedListeners list.
     * @param listener ViewportChangedListener
     */
    public synchronized void removeViewportListener(ViewportChangedListener listener) {
        viewportChangedListeners.remove(listener);
    }

    /**
     * notifies the MapController about each of the viewPortChangedEvents.
     */
    private synchronized void fireViewportChangedEvent() {
        if (viewportChangedListeners != null) {
            testTime = System.currentTimeMillis();
            for (ViewportChangedListener l : viewportChangedListeners) {
                l.ViewportChanged();
            }
            cachedImage = null;
        }
    }

    /**
     * method used for panning on the map.
     * @param startPoint the start PixelMapPoint of the panning
     * @param endPoint the end PixelMapPoint of the panning
     */
    public void pan(PixelMapPoint startPoint, PixelMapPoint endPoint) {
        panPixels(startPoint.getX() - endPoint.getX(), startPoint.getY() - endPoint.getY());
    }

    /**
     * calculates the scaleFactor of the current viewPort.
     */
    private void calcScaleFactor() {
        if (init) {
            scaleFactor = getWidth() / (viewport.getUpRight().getX() - viewport.getDownLeft().getX());
        }
    }

    /**
     * Zoom the map with a percentage value (negative values zooms out)
     * @param percent How much to zoom in percent
     */
    public void zoom(int percent, PixelMapPoint pmp) {
        if (percent < 0) {
            zoom(percent);
            return;
        }

        MapPoint m1 = pixelToUTM32(pmp);

        Viewport newViewport = viewport.clone();
        newViewport.zoom(percent);
        setViewport(newViewport);

        newViewport = viewport.clone();
        MapPoint m2 = pixelToUTM32(pmp);
        double diffX = m1.getX() - m2.getX();
        double diffY = m1.getY() - m2.getY();
        newViewport.pan(diffX, diffY);
        setViewport(newViewport);
    }

    /**
     * Zoom the map with a percentage value (negative values zooms out)
     * @param percent How much to zoom in percent
     */
    public void zoom(int percent) {
        Viewport newViewport = viewport.clone();
        newViewport.zoom(percent);
        setViewport(newViewport);
    }

    /**
     * Zoom to a given viewport. Viewport is enlarged in either height or
     * width to fit the window.
     * @param v The viewport to zoom to.
     */
    public void showViewport(Viewport v) {
        zoomUTM32(v.getDownLeft(), v.getUpRight());
    }

    /**
     * Zoom to a given viewport defined by two UTM32 coordinates. Viewport is
     * enlarged in either height or width to fit the window.
     * @param point1 DownLeft point of the selection
     * @param point2 UpRight point of the selection
     */
    private void zoomUTM32(MapPoint point1, MapPoint point2) {
        double x1 = point1.getX() < point2.getX() ? point1.getX() : point2.getX();
        double y1 = point1.getY() < point2.getY() ? point1.getY() : point2.getY();
        double width = Math.abs(point1.getX() - point2.getX());
        double height = Math.abs(point1.getY() - point2.getY());
        if (height < 100 || width < 100) {
            repaint();
            return;
        }
        double selectionProportion = width / height;
        double viewportProportion = (double) getWidth() / (double) getHeight();
        if (selectionProportion > viewportProportion) {
            double newHeight = width / viewportProportion;
            y1 -= (newHeight - height) / 2;
            height = newHeight;
        } else {
            double newWidth = height * viewportProportion;
            x1 -= (newWidth - width) / 2;
            width = newWidth;
        }
        setViewport(new Viewport(x1, y1, x1 + width, y1 + height));
    }

    /**
     * Zoom to a given viewport. The Viewport is enlarged in either height or
     * width to fit the window.
     * @param viewport The Viewport to zoom to
     */
    public void zoom(Viewport viewport) {
        zoomUTM32(viewport.getDownLeft(), viewport.getUpRight());
    }

    /**
     * zoom on the map using Pixel parameters and converting them to UTM32 values
     * @param point1 PixelMapPoint
     * @param point2 PixelMapPoint
     */
    private void zoomPixel(PixelMapPoint point1, PixelMapPoint point2) {
        zoomUTM32(pixelToUTM32(point1), pixelToUTM32(point2));
    }

    /**
     * Pan the map with a percentage value for x and y
     *
     * @param percentX Percent to pan x (accepts negative)
     * @param percentY Percent to pan y (accepts negative)
     */
    public void pan(int percentX, int percentY) {
        Viewport newViewport = viewport.clone();
        newViewport.pan(percentX, -percentY);
        setViewport(newViewport);
        this.setFocusable(true);
    }

    /**
     * pan on the map using a UTM32 value for x and y.
     *
     * @param changeX double how much should it pan on the x-axis
     * @param changeY double how much should it pan on the y-axis
     */
    public void panUTM32(double changeX, double changeY) {
        Viewport newViewport = viewport.clone();
        newViewport.pan(changeX, -changeY);
        setViewport(newViewport);
        this.setFocusable(true);
    }

    /**
     * pan on the map using a pixelvalue for x and y and getting the currentscalefactor.
     *
     * @param changeX int
     * @param changeY int
     */
    public void panPixels(int changeX, int changeY) {
        panUTM32(changeX / scaleFactor, changeY / scaleFactor);
    }

    /**
     * Pan the map north
     *
     * @param percent Percent to pan
     */
    public void panN(int percent) {
        pan(0, -percent);
    }

    /**
     * Pan the map north-east
     *
     * @param percent Percent to pan
     */
    public void panNE(int percent) {
        pan(percent, -percent);
    }

    /**
     * Pan the map east
     *
     * @param percent Percent to pan
     */
    public void panE(int percent) {
        pan(percent, 0);
    }

    /**
     * Pan the map south-east
     * @param percent Percent to pan
     */
    public void panSE(int percent) {
        pan(percent, percent);
    }

    /**
     * Pan the map south
     * @param percent Percent to pan
     */
    public void panS(int percent) {
        pan(0, percent);
    }

    /**
     * Pan the map south-west
     * @param percent Percent to pan
     */
    public void panSW(int percent) {
        pan(-percent, percent);
    }

    /**
     * Pan the map west
     * @param percent Percent to pan
     */
    public void panW(int percent) {
        pan(-percent, 0);
    }

    /**
     * Pan the map north-west
     * @param percent Percent to pan
     */
    public void panNW(int percent) {
        pan(-percent, -percent);
    }

    /**
     * Add a RoutePlanRequestListener to the MapView
     * @param listener The listener for the event
     */
    public void addRoutePlanRequestListener(RoutePlanRequestListener listener) {
        this.routePlanRequestListeners.add(listener);
    }

    /**
     * Remove a RoutePlanRequestListener to the MapView
     * @param listener The listener to be removed
     */
    public void removeRoutePlanRequestListener(RoutePlanRequestListener listener) {
        this.routePlanRequestListeners.remove(listener);
    }

    /**
     * if the autoRoutePlan field is selected a routePlanRequestEvent will be fired.
     */
    private void autoRoutePlan() {
        if (autoRoutePlan) {
            fireRoutePlanRequestEvent();
        }
    }

    /**
     * if there is more than one point in the ruotePlanRequest and
     * a means of transportation has been chosen, the routePlan will
     * automatically be calculated and drawn.
     */
    private void fireRoutePlanRequestEvent() {
        if (!rpr.isReady()) {
            removeRoutePlan();
            return;
        }
        for (RoutePlanRequestListener listener : routePlanRequestListeners) {
            listener.RoutePlanRequested(rpr);
        }
    }

    /**
     * add a MapMouseEventListener to the mapMouseEventListeners list.
     * @param listener the MapMouseEventListener to be added.
     */
    public void addMapMouseEventListener(MapMouseEventListener listener) {
        this.mapMouseEventListeners.add(listener);
    }

    /**
     * remove a MapMouseEventListener from the mapMouseEventListeners list.
     * @param listener the MapMouseEventListener to be added.
     */
    public void removeMapMouseEventListener(MapMouseEventListener listener) {
        this.mapMouseEventListeners.remove(listener);
    }

    /**
     * fire an event for each listener in mapMouseEventListeners list.
     * The mouse is moved using the values of a MapPoint.
     * @param pos The MapPoint used.
     */
    private void fireMapMouseMovedEvent(MapPoint pos) {
        for (MapMouseEventListener l : mapMouseEventListeners) {
            l.mouseMoved(pos);
        }
    }

    /**
     * how many metres does a pixel consist of.
     * @return double, pixel per meter.
     */
    double getPixelPerMeter() {
        return this.getWidth() / (viewport.getUpRight().getX() - viewport.getDownLeft().getX());
    }

    /**
     * how many metres does a specific viewport consist of.
     * @param viewport the Viewport used.
     * @return double, pixel per meter.
     */
    double getPixelPerMeter(Viewport viewport) {
        return this.getWidth() / (viewport.getUpRight().getX() - viewport.getDownLeft().getX());
    }

    /**
     * show all the routePlan buttons.
     */
    private void showRouteBtns(boolean show) {
        routePlanPanel.setVisible(show);
    }

    /**
     * clear the routePlan, and inform the user
     * that he has to select new points to ceate a new route.
     */
    void removeRoutePlan() {
        routePlan = null;
        controller.setStatusText("Klik for at tilføje punkter eller ændr evt. befordringsmiddel");
        repaint();
    }
}
